#pragma once
#include "MathUtils.h"

namespace Alice{
    /**
    * 16 bit float components and conversion
    *
    *
    * IEEE float 16
    * Represented by 10-bit mantissa M, 5-bit exponent E, and 1-bit sign S
    *
    * Specials:
    * 
    * E=0, M=0			== 0.0
    * E=0, M!=0			== Denormalized value (M / 2^10) * 2^-14
    * 0<E<31, M=any		== (1 + M / 2^10) * 2^(E-15)
    * E=31, M=0			== Infinity
    * E=31, M!=0		== NAN
    * 
    * conversion from 32 bit float is with RTNE (round to nearest even)
    *
    * Legacy code truncated in the conversion.  SetTruncate can be used for backwards compatibility.
    * 
    */
    class FFloat16
    {
    public:

        /* Float16 can store values in [-MaxF16Float,MaxF16Float] */
        constexpr static float MaxF16Float = 65504.f;

        uint16 Encoded;

        /** Default constructor */
        FFloat16();

        /** Copy constructor. */
        FFloat16(const FFloat16& FP16Value);

        /** Conversion constructor. Convert from Fp32 to Fp16. */
        FFloat16(float FP32Value);	

        /** Assignment operator. Convert from Fp32 to Fp16. */
        FFloat16& operator=(float FP32Value);

        /** Assignment operator. Copy Fp16 value. */
        FFloat16& operator=(const FFloat16& FP16Value);

        /** Convert from Fp16 to Fp32. */
        operator float() const;

        /** Convert from Fp32 to Fp16, round-to-nearest-even. (RTNE)
        Stores values out of range as +-Inf */
        void Set(float FP32Value);
        
        /*Convert from Fp32 to Fp16, round-to-nearest-even. (RTNE)
        Clamps values out of range as +-MaxF16Float */
        void SetClamped(float FP32Value)
        {
            Set( MathUtils::Clamp(FP32Value,-MaxF16Float,MaxF16Float) );
        }

        /** Convert from Fp32 to Fp16, truncating low bits. 
        (backward-compatible conversion; was used by Set() previously)
        Clamps values out of range to [-MaxF16Float,MaxF16Float] */
        void SetTruncate(float FP32Value);

        /** Set to 0.0 **/
        void SetZero()
        {
            Encoded = 0;
        }
        
        /** Set to 1.0 **/
        void SetOne()
        {
            Encoded = 0x3c00;
        }

        /** Return float clamp in [0,MaxF16Float] , no negatives or infinites or nans returned **/
        FFloat16 GetClampedNonNegativeAndFinite() const;
        
        /** Return float clamp in [-MaxF16Float,MaxF16Float] , no infinites or nans returned **/
        FFloat16 GetClampedFinite() const;

        /** Convert from Fp16 to Fp32. */
        float GetFloat() const;

        /** Is the float negative without converting
        NOTE: returns true for negative zero! */
        bool IsNegative() const
        {
            // negative if sign bit is on
            // can be tested with int compare
            return (int16)Encoded < 0;
        }
    };
}